Skip to content

fix(rendering): make SpriteEcsComponent.pivot Y-up to match the rest of the engine - #590

Merged
stormmuller merged 1 commit into
devfrom
claude/github-issue-585-7s9m00
Aug 7, 2026
Merged

fix(rendering): make SpriteEcsComponent.pivot Y-up to match the rest of the engine#590
stormmuller merged 1 commit into
devfrom
claude/github-issue-585-7s9m00

Conversation

@stormmuller

Copy link
Copy Markdown
Member

Summary

SpriteEcsComponent.pivot used a Y-down convention ((0, 0) was the sprite's top-left) while world position, rotation, and every other Y-facing value in the engine are Y-up. The mismatch was an internal projection detail (the vertex shader flips Y before the projection matrix flips it back for position/rotation) leaking through pivot's public API - invisible at the default centered pivot, which is why it survived.

Traced the full pipeline per the issue and confirmed the analysis: bindSpriteInstanceData pre-negates position.world.y and rotation.world to compensate for the projection's Y flip, but sprite.vert.glsl computes the pivot-based local offset after that compensation, so it never gets flipped - pivot.y = 0 landed at the sprite's top instead of its bottom.

Changes

  • sprite.vert.glsl: negate a_instancePivot.y alongside the existing [0,1] -> [-1,1] doubling, so pivot's Y now gets the same flip position/rotation already get. (0, 0) is now bottom-left, (1, 1) is top-right.
  • computeNineSliceRegions (compute-nine-slice-regions.ts): updated the region-offset math ((1 - pivot.y) * height - ... instead of pivot.y * height - ...) so nine-slice region placement agrees with the corrected shader convention. The centered pivot (0.5, 0.5) produces identical output before and after (1 - 0.5 === 0.5), so this is a no-op for every sprite in the repo (no demo or test uses a non-centered pivot).
  • sprite-component.ts: updated pivot's JSDoc to describe the new Y-up convention.
  • compute-nine-slice-regions.test.ts: replaced the single top-left-pivot test with a parameterized case pinning all four corners (bottom-left, bottom-right, top-left, top-right), per the issue's test-coverage callout.
  • CHANGELOG.md: added a Fixed entry under [Unreleased], flagged as a breaking change for non-centered pivots.

Related issue(s)

Closes #585

Out of scope

The issue's design doc reference (design/ui-system.md's pivot bridge, from #580) isn't in dev yet, so there's nothing to remove there in this PR.

Verification checklist

  • npm run check-types passes with 0 errors
  • npm test passes (1036 tests)
  • npm run lint passes with 0 errors (pre-existing, unrelated TODO warnings only)
  • npm run cspell passes with 0 errors
  • npm run check-exports passes
  • No public API surface changed beyond pivot's documented semantics - no export changes needed
  • Documentation under /documentation-site/docs/docs - no page documents the pivot convention, so nothing to update
  • No demo uses a non-centered pivot (verified via grep), so no demo code changes; still built /dist, ran documentation-site's typecheck/build, and visually checked the nine-slice and brick-breaker demos in a browser to confirm no regression at the default centered pivot

Changelog

  • Added a bullet under ## [Unreleased]#### Fixed in CHANGELOG.md

Generated by Claude Code

…of the engine

pivot used a Y-down convention ((0, 0) was the sprite's top-left) while
world position, rotation, and every other Y-facing value are Y-up. The
mismatch was an internal projection detail (the shader flips Y) leaking
through pivot's public API, invisible at the default centered pivot.

Flips pivot's Y in sprite.vert.glsl before it's combined with the quad's
local position, and updates computeNineSliceRegions' pivot handling to
match so nine-slice region placement agrees with the corrected shader.

Breaking change for content using a non-centered pivot.

Fixes #585
@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@stormmuller

Copy link
Copy Markdown
Member Author

In the ECS demo. The position looks correct, but the rotation looks inverted?

Copy link
Copy Markdown
Member Author

Looked into this — this PR doesn't touch rotation at all, and for a centered pivot (which the ECS demo's star sprite uses, via createImageSprite's default pivot: { x: 0.5, y: 0.5 }) the shader's normalizedPivot is (0, 0) both before and after this change ((0.5 - 0.5) * 2 = 0, negated or not), so the rendered output for that demo should be byte-for-byte identical to dev.

To double-check rather than just reason about it, I tracked the star's actual on-screen rotation with a frame-diff (rotate-and-correlate) measurement against two captured frames ~520ms apart on this branch's build: the star rotates ~30.5° counter-clockwise on screen in that window, matching rotation.world's expected +29.9° change (Δt · 1 rad/s, since the demo sets rotation.world = time.timeInSeconds directly) and the engine's documented positive-rotation-is-CCW-in-Y-up-space convention (same convention bindSpriteInstanceData's rotation negation targets, and the same one physics/Vector2.rotate use elsewhere).

So the rotation direction looks correct to me, and unrelated to this PR either way — happy to be pointed at a specific frame/timestamp if it still looks inverted to you, or if you meant something else by "rotation" (e.g. the orbit path direction, which is independent of the sprite's own spin and also unchanged by this diff).


Generated by Claude Code

@stormmuller
stormmuller merged commit ea7c3c9 into dev Aug 7, 2026
13 checks passed
@stormmuller
stormmuller deleted the claude/github-issue-585-7s9m00 branch August 7, 2026 18:04
stormmuller pushed a commit that referenced this pull request Aug 8, 2026
…ivot fix

dev's #590 flipped SpriteEcsComponent.pivot to Y-up ((0, 0) bottom-left,
(1, 1) top-right) and fixed computeNineSliceRegions to match, but
shapeText has its own separate pivot math that was explicitly modeled on
nine-slice's old (Y-down) formula. That's now inconsistent with the rest
of the engine, including the sprite instancing pipeline text's own glyph
quads render through.

Applies the same fix nine-slice got: pivot.y's contribution flips via
(1 - pivot.y) instead of pivot.y directly. Only affects a non-centered
pivot; the default (0.5, 0.5) is unaffected. Updates the pivot tests and
docs to match.

Also merges dev in to pick up #590 and everything else merged since this
branch was created.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113gCpCEDFQad8Y9dJuahSq
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(rendering): SpriteEcsComponent.pivot is Y-down while the rest of the engine is Y-up

2 participants